Omit file extensions in links

Pitfall By design
Docly serves every page at a clean, extensionless URL and resolves the underlying file by name, so internal links must omit the .html and .hash suffix. A link that includes the extension still resolves — it is not broken — but it produces a non-canonical, noisier URL and splits SEO signals between two addresses for the same page.

What you'll see

A developer — or, more often, an AI code-generation tool — writes an internal link that includes the source file's on-disk extension, addressing a page by its filename:

<a href="/Guidelines/Getting-started.html">Getting started</a>
<a href="/Products/Pricing.hash">Pricing</a>

The link works — Docly resolves the file and the page loads — so nothing looks wrong in testing. But the address bar now reads /Guidelines/Getting-started.html instead of the clean /Guidelines/Getting-started, and that suffixed URL is the one that gets shared, bookmarked, indexed, and linked back to.

What's actually happening

Docly serves pages at clean, extensionless URLs. The .hash, .html, and .docly extension is an authoring detail — it identifies the source file on the drive, not the address the page is published at. A hash file at #/Products/Pricing.hash is served at /Products/Pricing; a documentation document is served the same way, with spaces turned into hyphens and the extension dropped.

The lookup is by name, and that is the key point: when a request arrives, Docly resolves the matching file whether or not the extension is present. A link written as /Products/Pricing.hash is therefore not broken — it returns exactly the same page as /Products/Pricing. That is what makes the mistake easy to miss: it never produces a 404, only a worse URL.

The cost is twofold. Noise: the extension leaks an implementation detail — that the page happens to be a hash template today — into a permanent, shareable address. If the page is later rebuilt as static HTML, the .hash URL becomes a lie, yet old links and search results still point at it. SEO: the same content is now reachable at two distinct URLs (/Pricing and /Pricing.hash). Search engines treat those as separate pages serving duplicate content, splitting inbound link equity and ranking signals between them and potentially indexing the uglier one as canonical. Clean URLs are also shorter and more memorable, which helps both users and ranking.

What to do

Link to the clean path — the page's name with no extension. That is the canonical URL Docly itself emits, and the one you want shared and indexed.

Do — extensionless page links:

<a href="/Guidelines/Getting-started">Getting started</a>
<a href="/Products/Pricing">Pricing</a>

Don't — extension baked into the URL:

<a href="/Guidelines/Getting-started.html">Getting started</a>
<a href="/Products/Pricing.hash">Pricing</a>

For a section or landing page, link to a folder with an index file — not to an extensioned file. A clean URL like /products/ comes from a folder that contains an Index.hash or Index.html; Docly serves that index file for the folder automatically, so the address needs neither a filename nor an extension. Link to the folder with a trailing slash, and reserve the bare site root / for the home page (served by the Index.hash/Index.html in #/Root):

<a href="/">Home</a>
<a href="/products/">Products</a>

These root-relative links are also publish-safe: when an app is mounted under a subfolder, Docly automatically rewrites leading-slash href and src attributes in static HTML to the publish location, so / and /products/ keep working with no change. That rewriting does not cover URLs your JavaScript builds at runtime — use ~/ there. See Use tilde paths in JavaScript for the full rule, and Setting up default files for how index files resolve.

Apply the same rule anywhere a page URL is emitted, not just <a href>: <link rel="canonical">, Open Graph og:url tags, sitemap entries, redirects, and window.location assignments. Use the extensionless form everywhere so a single canonical URL accumulates all the SEO signal.

This rule is about page URLs only. It does not apply to references that address the source file on the drive — the file="Header.hash" argument of an include directive, or a path passed to a filesystem function such as getFile — which legitimately need the real filename and its extension. The rule is specifically: don't let that on-disk extension leak out into a public link or URL.

For giving uploaded files and images clean, SEO-friendly addresses from template code, see Linking images and files in hash.